﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.Threading;

namespace game
{
    // TODO : add and remove methods for multilayer object lists

    public class Scene : IEnumerable<GameObject>
    {
        public ExplosionManager ExplosionMgr { get; protected set; }

        public Ship PlayerObject { get; set; }

        public BillboardManager BilboardMgr { get; protected set; }

        public SoundManager SoundMgr { get; protected set; }

        public List<Layer> Layers { get; protected set; }
        public List<Camera> Cameras { get; protected set; }
        public List<GameObject> MultiLayerObjects { get; protected set; }
        public List<Army> Armies { get; set; }
        public SpaceBox SpaceBox { get; set; }

        public Texture2D DepthTexture { get; set; }

        public Billboard planet1;
        public Billboard planet2;
        public Billboard planet3;
        public Billboard cloud;

        public List<PointLight> Lights { get; protected set; }

        public Camera ActiveCamera
        {
            get { return activeCamera; }
            set
            {
                activeCamera = value;
                if (!Cameras.Contains(value))
                {
                    Cameras.Add(value);
                }
            }
        }
        private Camera activeCamera = null;

        private Queue<GameObject> multilayerObjectsToAdd;
        private Queue<GameObject> multilayerObjectsToRemove;

        public Scene()
        {
            BilboardMgr = new BillboardManager(this);
            Cameras = new List<Camera>();
            Layers = new List<Layer>();
            MultiLayerObjects = new List<GameObject>();
            ExplosionMgr = new ExplosionManager(this);
            SoundMgr = new SoundManager(this);

            Lights = new List<PointLight>();

            multilayerObjectsToAdd = new Queue<GameObject>();
            multilayerObjectsToRemove = new Queue<GameObject>();
        }

        public void Initialize()
        {

        }


        public void AddObject(GameObject obj, int layerIndex)
        {
            Layers[layerIndex].AddObject(obj);
        }

        public void AddObject(GameObject obj, int lowestLayerIndex, int highestLayerIndex)
        {
            for (int i = lowestLayerIndex; i <= highestLayerIndex; i++)
            {
                Layers[i].AddObject(obj);
            }
            multilayerObjectsToAdd.Enqueue(obj);
        }

        public void RemoveObject(GameObject obj)
        {
            if (obj.IsMultiLayer)
            {
                for (int i = obj.LLayerIndex; i <= obj.HLayerIndex; i++)
                {
                    Layers[i].RemoveObject(obj);
                }
                multilayerObjectsToRemove.Enqueue(obj);
            }
            else
            {
                Layers[obj.LayerIndex].RemoveObject(obj);
            }
        }

        public void Update(GameTime gameTime)
        {
            if (Input.Keyboard.IsKeyDown(Keys.Escape))
            {
                ShooterGame.Instance.Exit();
            }
            if (Input.Keyboard.IsKeyDown(Keys.P))
            {
                foreach (GameObject obj in this)
                {
                    if (obj is AIFighter)
                    {
                        Layers[obj.LayerIndex].RemoveObject(obj);
                    }
                }
            }

            MovementControl.DeltaTimeMs = gameTime.ElapsedGameTime.Milliseconds;

            while (multilayerObjectsToAdd.Count > 0)
                MultiLayerObjects.Add(multilayerObjectsToAdd.Dequeue());
            while (multilayerObjectsToRemove.Count > 0)
                MultiLayerObjects.Remove(multilayerObjectsToRemove.Dequeue());

            SpaceBox.Update(gameTime);

            BilboardMgr.Update(gameTime);

            foreach (Layer layer in Layers)
            {
                layer.Update(gameTime);
            }

            foreach (GameObject obj in this)
            {
                obj.Update(gameTime);
            }

            foreach (Layer layer in Layers)
            {
             //   if(Input.Keyboard.IsKeyDown(Keys.Y))
                    layer.CheckCollisions();
            }

            if (activeCamera != null)
            {
                activeCamera.Update(gameTime);
            }

            

            for (int i = 0; i < Lights.Count; i++)
            {
                PointLight light = Lights[i];
                if (!light.IsAlive)
                {
                    Lights.Remove(light);
                    i--;
                }
                else
                {
                    light.Update(gameTime);
                }
            }

            ExplosionMgr.Update(gameTime);

            SoundMgr.Update(gameTime);

        }

        public void Draw(GameTime gameTime)
        {
            ShooterGame.Instance.GraphicsDevice.RenderState.AlphaBlendEnable = false;
            ShooterGame.Instance.GraphicsDevice.RenderState.DepthBufferWriteEnable = true;
            ShooterGame.Instance.GraphicsDevice.RenderState.PointSpriteEnable = false;

            // drawing far environment
            ShooterGame.Instance.GraphicsDevice.RenderState.DepthBufferEnable = false;

            SpaceBox.DrawOpaque(gameTime);

            // drawing opaque stuff
            ShooterGame.Instance.GraphicsDevice.RenderState.DepthBufferEnable = true;

            foreach (GameObject obj in this)
            {
                if (ActiveCamera.Frustum.Contains(obj.GetBSphereTransformed())
                        != ContainmentType.Disjoint)
                {
                    obj.DrawOpaque(gameTime);
                }
            }

            if (Input.Keyboard.IsKeyDown(Keys.B))
            {
                ShooterGame.Instance.GraphicsDevice.RenderState.DepthBufferEnable = false;
                foreach (GameObject obj in this)
                {
                    BoundingSphereRenderer.Render(obj.GetBSphereTransformed(),
                        ShooterGame.Instance.GraphicsDevice, ActiveCamera.View, ActiveCamera.Projection,
                        Color.CornflowerBlue);
                }
                ShooterGame.Instance.GraphicsDevice.RenderState.DepthBufferEnable = true;
            }

            planet1.Draw(gameTime);
            planet2.Draw(gameTime);
            planet3.Draw(gameTime);
            //cloud.Draw(gameTime);

            // drawing transparent stuff
            foreach (GameObject obj in this)
            {
                obj.Draw(gameTime, GameObject.DrawPhase.Transparent);
            }

            ExplosionMgr.Draw(gameTime);

            BilboardMgr.Draw(gameTime);
        }


        public void DrawObjectsWithCustomShader(GameTime gameTime, SingleMatrixShader shader)
        {
            foreach (GameObject obj in this)
            {
                obj.DrawWithCustomShader(gameTime, shader);
            }
        }

        public float GetLayerHeight(int layerIndex)
        {
            return Layers[layerIndex].Height;
        }

        public int GetTopLayerIndex()
        {
            return Layers.Count - 1;
        }

        public int GetBottomLayerIndex()
        {
            return 0;
        }

        public Ship GetClosestEnemyOnLayer(Ship ship)
        {
            return Layers[ship.LayerIndex].GetClosestEnemy(ship);
        }

        public int GetClosestLayerWithEnemy(Ship ship)
        {
            int curr = ship.LayerIndex;
            int min = GetBottomLayerIndex();
            int max = GetTopLayerIndex();

            int nextDown = curr;
            int nextUp = curr;

            bool enemyFound = false;
            int enemyLayer = -1;

            while (!enemyFound)
            {
                if (nextDown > min)
                {
                    nextDown--;
                    if (Layers[nextDown].AreAnyEnemies(ship))
                    {
                        enemyFound = true;
                        enemyLayer = nextDown;
                    }
                }

                if (nextUp < max)
                {
                    nextUp++;
                    if (Layers[nextUp].AreAnyEnemies(ship))
                    {
                        enemyFound = true;
                        enemyLayer = nextUp;
                    }
                }

                if (nextUp >= max && nextDown <= min)
                {
                    break;
                }
            }

            return enemyLayer;
        }

        public void ShockWave(Vector3 position, float strengthScale)
        {
            Vector3 distance;
            Vector3 playerPosition = PlayerObject.Position;
            Vector3.Subtract(ref position, ref playerPosition, out distance);
            float dist = distance.Length();
            //if (dist < 100000.0f)
            {
                activeCamera.Shake(5000.0f * strengthScale / dist);
            }
            //else if (dist < 300000.0f && strengthScale > 10.0f)
            //{
            //    activeCamera.Shake(1.0f);
            //}
        }

        #region IEnumerable<GameObject> Members

        public IEnumerator<GameObject> GetEnumerator()
        {
            foreach (Layer layer in Layers)
            {
                foreach (GameObject obj in layer)
                {
                    yield return obj;
                }
            }

            foreach (GameObject obj in MultiLayerObjects)
            {
                yield return obj;
            }
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}
